﻿// Autore: Alessio Parma

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;

namespace RecipesManager.Data.Operations
{
    public enum GuestType : byte
    {
        Necessary = 0, // Se vi è almeno un ospite nella lista con cui l'ospite dato ha relazioni
        Recommended = 1, // Se la somma pesata dei voti è maggiore di zero
        Common = 2, // Se la somma pesata dei voti è uguale a zero
        NotRecommended = 3 // Se la somma pesata dei voti è minore di zero
    }

    public enum RecipeType : byte
    {
        Recommended = 0, // Se la somma pesata dei voti è maggiore di zero
        Common = 1, // Se la somma pesata dei voti è uguale a zero
        NotRecommended = 2, // Se la somma pesata dei voti è minore di zero
        Dangerous = 3, // Se vi è almeno un ospite con un'intolleranza
    }

    public enum IssueType : byte
    {
        Warning = 0,
        Error = 1 // Relazioni non rispettate, intolleranze non rispettate
    }

    public sealed class Classifier
    {
        readonly GuestClassifier _guestClassifier;       
        readonly RecipeClassifier _recipeClassifier;
        readonly ObservableCollection<Issue> _issues;
        readonly ICollection<IGuest> _currGuests;
        readonly ICollection<IRecipe> _currRecipes; 

        public Classifier(IEnumerable<IGuest> currGuests, IEnumerable<IRecipe> currRecipes)
        {
            Contract.Requires(currGuests != null && currRecipes != null);
            Contract.Requires(Contract.ForAll(currGuests, g => Manager.Guests.Contains(g.Id)));
            Contract.Requires(Contract.ForAll(currRecipes, r => Manager.Recipes.Contains(r.Id)));

            _issues = new ObservableCollection<Issue>();
            _currGuests = new HashSet<IGuest>();
            _currRecipes = new HashSet<IRecipe>();

            var guests = Manager.Guests.Unordered.ToList();
            _guestClassifier = new GuestClassifier(guests, this);
            _recipeClassifier = new RecipeClassifier(guests, this);

            foreach (var g in currGuests) OnGuestAdd(g);
            foreach (var r in currRecipes) OnRecipeAdd(r);
        }

        [Pure]
        public ObservableCollection<Issue> Issues
        {
            get { return _issues; }
        }

        [Pure]
        public ObservableCollection<RatedGuest> RatedGuests
        {
            get { return _guestClassifier.RatedGuests; }
        }

        [Pure]
        public ObservableCollection<RatedRecipe> RatedRecipes
        {
            get { return _recipeClassifier.RatedRecipes; }
        }

        public void OnGuestAdd(string guestName)
        {
            Contract.Requires(Manager.Guests.Contains(guestName));
            Contract.Ensures(RatedGuests.All(rg => rg.Guest.Name != guestName));
            Contract.Ensures(_currGuests.Count(g => g.Name == guestName) == 1);
            
            OnGuestAdd(Manager.Guests[guestName]);        
        }

        public void OnGuestRemove(string guestName)
        {
            Contract.Requires(Manager.Guests.Contains(guestName));
            Contract.Ensures(RatedGuests.Count(rg => rg.Guest.Name == guestName) == 1);
            Contract.Ensures(_currGuests.Count(g => g.Name == guestName) == 0);

            OnGuestRemove(Manager.Guests[guestName]);           
        }

        public void OnRecipeAdd(string recipeName)
        {
            Contract.Requires(Manager.Recipes.Contains(recipeName));
            Contract.Ensures(RatedRecipes.All(rr => rr.Recipe.Name != recipeName));
            Contract.Ensures(_currRecipes.Count(r => r.Name == recipeName) == 1);
            
            OnRecipeAdd(Manager.Recipes[recipeName]);         
        }

        public void OnRecipeRemove(string recipeName)
        {
            Contract.Requires(Manager.Recipes.Contains(recipeName));
            Contract.Ensures(RatedRecipes.Count(rr => rr.Recipe.Name == recipeName) == 1);
            Contract.Ensures(_currRecipes.Count(r => r.Name == recipeName) == 0);

            OnRecipeRemove(Manager.Recipes[recipeName]);        
        }

        [Pure]
        internal bool Contains(IGuest guest)
        {
            return _currGuests.Contains(guest);
        }

        [Pure]
        internal bool Contains(IRecipe recipe)
        {
            return _currRecipes.Contains(recipe);
        }

        void OnGuestAdd(IGuest guest)
        {
            Debug.Assert(!_currGuests.Contains(guest));
            _currGuests.Add(guest);
            _guestClassifier.OnGuestAdd(guest);
            _recipeClassifier.OnGuestAdd(guest);
        }

        void OnGuestRemove(IGuest guest)
        {
            Debug.Assert(_currGuests.Contains(guest));
            _guestClassifier.OnGuestRemove(guest);
            _recipeClassifier.OnGuestRemove(guest);
            _currGuests.Remove(guest);
        }

        void OnRecipeAdd(IRecipe recipe)
        {
            Debug.Assert(!_currRecipes.Contains(recipe));
            _currRecipes.Add(recipe);
            _recipeClassifier.OnRecipeAdd(recipe);
        }

        void OnRecipeRemove(IRecipe recipe)
        {
            Debug.Assert(_currRecipes.Contains(recipe));
            _recipeClassifier.OnRecipeRemove(recipe);
            _currRecipes.Remove(recipe);
        }

        [ContractInvariantMethod]
        void Invariant()
        {
            Contract.Invariant(RatedGuests != null);
            Contract.Invariant(RatedRecipes != null);
        }
    }

    public sealed class RatedGuest
    {
        readonly IGuest _guest;

        internal RatedGuest(IGuest guest)
        {
            _guest = guest;
            Type = GuestType.Common;
        }

        [Pure]
        public IGuest Guest
        {
            get { return _guest; }
        }

        [Pure]
        public GuestType Type { get; internal set; }

        [Pure]
        public string TypeDescription
        {
            get { return Type.Description(); }
        }

        public override string ToString()
        {
            return string.Format("{0}, {1}", _guest.Name, Type.Description());
        }
    }

    public sealed class RatedRecipe : INotifyPropertyChanged
    {
        readonly IRecipe _recipe;

        internal RatedRecipe(IRecipe recipe)
        {
            _recipe = recipe;
            Type = RecipeType.Common;
        }

        [Pure]
        public string Description
        {
            get { return ToString(); }
        }

        [Pure]
        public int Rating { get; internal set; }

        [Pure]
        public IRecipe Recipe
        {
            get { return _recipe; }
        }

        [Pure]
        public RecipeType Type { get; internal set; }

        [Pure]
        public string TypeDescription
        {
            get { return Type.Description(); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal void UpdateCollection()
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs("Type"));
        }

        public override string ToString()
        {
            var score = (Rating == int.MinValue) ? "Molto basso" : Rating.ToString();
            return string.Format("{0}, voto: {1}", _recipe.Name, score);
        }
    }

    public sealed class Issue
    {
        readonly string _message;
        readonly IssueType _type;

        Issue(string message, IssueType type)
        {
            _message = message;
            _type = type;
        }

        [Pure]
        public string Message
        {
            get { return _message; }
        }

        [Pure]
        public IssueType Type
        {
            get { return _type; }
        }

        [Pure]
        public string TypeDescription
        {
            get { return _type.Description(); }
        }

        [Pure]
        internal static Issue ForCategoryRating(IGuest guest, ICategory category, IRecipe recipe, FoodRating rating)
        {
            Debug.Assert(rating == FoodRating.Bad || rating == FoodRating.VeryBad);

            const string fmt = "La categoria \"{0}\", presente nella ricetta \"{1}\" è {2} sgradito per \"{3}\".";
            var t = TypeForRating(rating);
            var msg = string.Format(fmt, category.Name, recipe.Name, t.Item1, guest.Name);
            return new Issue(msg, t.Item2);
        }

        [Pure]
        internal static Issue ForCategoryIntolerance(IGuest guest, ICategory category, IRecipe recipe)
        {
            const string fmt = "L'ospite \"{0}\" è intollerante alla categoria \"{1}\", presente nella ricetta \"{2}\".";
            var msg = string.Format(fmt, guest.Name, category.Name, recipe.Name);
            return new Issue(msg, IssueType.Error);
        }

        [Pure]
        internal static Issue ForIngredientRating(IGuest guest, IIngredient ingredient, IRecipe recipe,
                                                  FoodRating rating)
        {
            Debug.Assert(rating == FoodRating.Bad || rating == FoodRating.VeryBad);

            const string fmt = "L'ingrediente \"{0}\", presente nella ricetta \"{1}\" è {2} sgradito per \"{3}\".";
            var t = TypeForRating(rating);
            var msg = string.Format(fmt, ingredient.Name, recipe.Name, t.Item1, guest.Name);
            return new Issue(msg, t.Item2);
        }

        [Pure]
        internal static Issue ForIngredientIntolerance(IGuest guest, IIngredient ingredient, IRecipe recipe)
        {
            const string fmt =
                "L'ospite \"{0}\" è intollerante all'ingrediente \"{1}\", presente nella ricetta \"{2}\".";
            var msg = string.Format(fmt, guest.Name, ingredient.Name, recipe.Name);
            return new Issue(msg, IssueType.Error);
        }

        [Pure]
        internal static Issue ForGuestRating(IGuest whoRates, IGuest whoIsRated, GuestRating rating)
        {
            Debug.Assert(rating == GuestRating.Bad || rating == GuestRating.VeryBad);

            const string fmt = "L'ospite \"{0}\" è {1} sgradito per \"{2}\".";
            var t = TypeForRating(rating);
            var msg = string.Format(fmt, whoIsRated.Name, t.Item1, whoRates.Name);
            return new Issue(msg, t.Item2);
        }

        [Pure]
        internal static Issue ForRecipeRating(IGuest guest, IRecipe recipe, FoodRating rating)
        {
            Debug.Assert(rating == FoodRating.Bad || rating == FoodRating.VeryBad);

            const string fmt = "La ricetta \"{0}\" è {1} sgradita per \"{2}\".";
            var t = TypeForRating(rating);
            var msg = string.Format(fmt, recipe.Name, t.Item1, guest.Name);
            return new Issue(msg, t.Item2);
        }

        [Pure]
        internal static Issue ForRelationship(IGuest present, IGuest missing)
        {
            const string fmt = "L'ospite \"{0}\" è in relazione con \"{1}\", ma \"{1}\" non è stato invitato.";
            var msg = string.Format(fmt, present.Name, missing.Name);
            return new Issue(msg, IssueType.Error);
        }

        public override string ToString()
        {
            return _message;
        }

        static Tuple<string, IssueType> TypeForRating(FoodRating rating)
        {
            return (rating == FoodRating.Bad)
                       ? Tuple.Create("", IssueType.Warning)
                       : Tuple.Create("molto", IssueType.Error);
        }

        static Tuple<string, IssueType> TypeForRating(GuestRating rating)
        {
            return (rating == GuestRating.Bad)
                       ? Tuple.Create("", IssueType.Warning)
                       : Tuple.Create("molto", IssueType.Error);
        }
    }

    public static class ExtensionMethods
    {
        [Pure]
        public static string Description(this GuestType type)
        {
            switch (type)
            {
                case GuestType.Common:
                    return "Normali";
                case GuestType.Necessary:
                    return "Necessari";
                case GuestType.Recommended:
                    return "Consigliati";
                case GuestType.NotRecommended:
                    return "Non consigliati";
                default:
                    throw new Exception("Enum non valida!");
            }
        }

        [Pure]
        public static string Description(this RecipeType type)
        {
            switch (type)
            {
                case RecipeType.Common:
                    return "Normali";
                case RecipeType.Dangerous:
                    return "Pericolose";
                case RecipeType.Recommended:
                    return "Consigliate";
                case RecipeType.NotRecommended:
                    return "Non consigliate";
                default:
                    throw new Exception("Enum non valida!");
            }
        }

        [Pure]
        public static string Description(this IssueType type)
        {
            switch (type)
            {
                case IssueType.Error:
                    return "Errore";
                case IssueType.Warning:
                    return "Avviso";
                default:
                    throw new Exception("Enum non valida!");
            }
        }
    }
}